#pragma once

#include "../Utility/KRTMath.h"

#include "../Core/Ray.h"

namespace krt {

	class KRT_API Transform {
	public:
		Transform(const glm::mat4& trans = glm::mat4(1.0f));

		Transform(const glm::mat4& trans, const glm::mat4& intrans);

		glm::mat4 GetTrans() const { return transMat; }
		glm::mat4 GetIntrans() const { return intransMat; }

		glm::mat4& GetTrans() { return transMat; }
		glm::mat4& GetIntrans() { return intransMat; }

		bool operator==(const Transform& trans) const {
			return trans.transMat == this->transMat && trans.intransMat == this->intransMat;
		}

	private:
		// transform & inversed matrix
		mutable glm::mat4 transMat, intransMat;

	public:
		bool HasSacle() const {
			float ax = KRTMath::MagnitudeSq(this->transMat * glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
			float ay = KRTMath::MagnitudeSq(this->transMat * glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
			float az = KRTMath::MagnitudeSq(this->transMat * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
#define NOT_ONE(x) ((x) < .999f || (x) > 1.001f)
			return NOT_ONE(ax) || NOT_ONE(ay) || NOT_ONE(az);
#undef NOT_ONE
		}

		bool SwapHandedness() const {
			// determinant of left-top 3x3 matrix
			float det = (
				(transMat[0][0] * (transMat[1][1] * transMat[2][2] - transMat[1][2] * transMat[2][1])) -
				(transMat[0][1] * (transMat[1][0] * transMat[2][2] - transMat[1][2] * transMat[2][0])) +
				(transMat[0][2] * (transMat[1][0] * transMat[2][1] - transMat[1][1] * transMat[2][0]))
				);
			return det < 0.f;
		}

		inline glm::vec3 Trans(const glm::vec3& v) const {
			return transMat * glm::vec4(v, 1.0f);
		}

		inline void Trans(const glm::vec3& v, glm::vec3& outv) const {
			outv = transMat * glm::vec4(v, 1.0f);
		}

		inline glm::vec3 TransNormal(const glm::vec3& nor) const {
			//return glm::transpose(intransMat) * glm::vec4(nor, 1.0f);
			float x = nor.x, y = nor.y, z = nor.z;
			return glm::vec3(
				intransMat[0][0] * x + intransMat[1][0] * y + intransMat[2][0] * z,
				intransMat[0][1] * x + intransMat[1][1] * y + intransMat[2][1] * z,
				intransMat[0][2] * x + intransMat[1][2] * y + intransMat[2][2] * z
				);
		}

		inline Ray Trans(const Ray& ray) const {
			Ray reRay = ray;
			Trans(reRay.origin, reRay.origin);
			Trans(reRay.direct, reRay.direct);
			return reRay;
		}

		//  TODO: BBox

		inline Transform Trans(const Transform& trans) {
			return Transform(this->transMat * trans.transMat, trans.intransMat * this->intransMat);
		}

	public:
		static Ref<Transform> Create(const glm::vec3& position, const glm::vec3& rotation, float angle, const glm::vec3& scale);

		static Ref<Transform> Create(const glm::vec3& position, const glm::vec3& scale);

		static Transform Inverse(const Transform& t);

		static Ref<Transform> Inverse(Ref<Transform> t);

	};

}